package com.example.demo.handler;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.*;

import static com.example.demo.Constant.*;

/**
 * @author ListJiang
 * @since 2021/10/21
 * description 自定义Oauth2登录授权成功处理器
 */
public class CustomOauth2AuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    protected final Log log = LogFactory.getLog(this.getClass());

    private final InMemoryUserDetailsManager userDetailsService;
    private final RedisTemplate redisTemplate;

    public CustomOauth2AuthenticationSuccessHandler(InMemoryUserDetailsManager userDetailsService,
                                                    RedisTemplate redisTemplate) {
        this.userDetailsService = userDetailsService;
        this.redisTemplate = redisTemplate;
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws ServletException, IOException {
        customAuthentication(authentication);
        super.onAuthenticationSuccess(request, response, authentication);
    }

    private void customAuthentication(Authentication authentication) {
        // 保存Oauth2登录获得的用户
        Object principal = authentication.getPrincipal();
        if (principal instanceof DefaultOidcUser) {
            String name = ((DefaultOidcUser) principal).getName();

            // 重新构建权限集合
            Collection<? extends GrantedAuthority> authorities = ((DefaultOidcUser) principal).getAuthorities();
            Set<GrantedAuthority> au = new HashSet<>();
            for (GrantedAuthority authority : authorities) {
                au.add(authority);
            }

            // 构建本地用户准备入本地用户库
            User.UserBuilder builder = User.withDefaultPasswordEncoder()
                    .username(name)
                    .authorities(authorities)
                    .password(DEFAULT_PASSWORD);
            if (userDetailsService.userExists(name)) {
                UserDetails userDetails = userDetailsService.loadUserByUsername(name);
                for (GrantedAuthority authority : userDetails.getAuthorities()) {
                    au.add(authority);
                }
                userDetailsService.updateUser(builder.authorities(Collections.unmodifiableSet(au)).build());
            } else {
                UserDetails user = builder.authorities(Collections.unmodifiableSet(au)).build();
                userDetailsService.createUser(user);
            }

            // 构建本地token
            String authToken =
                    new String(Base64.getEncoder().encode((name + ":" + DEFAULT_PASSWORD).getBytes(StandardCharsets.UTF_8)));

            // 重构oauth2的authentication，添加本系统该用户已存在的权限。
            // 并将其信息放入到security上下文对象中
            if (authentication instanceof OAuth2AuthenticationToken) {
                authentication =
                        new OAuth2AuthenticationToken(((OAuth2AuthenticationToken) authentication).getPrincipal(),
                                Collections.unmodifiableSet(au),
                                ((OAuth2AuthenticationToken) authentication).getAuthorizedClientRegistrationId());
                SecurityContextHolder.getContext().setAuthentication(authentication);
                redisTemplate.opsForValue().set(TOKEN_REDIS_KEY + authToken, authentication,
                        TOKEN_TIMEOUT);

                if (log.isDebugEnabled()) {
                    log.debug("自定义oauth2登录成功处理器 : " + authentication.getName());
                }
            }
        }
    }
}